﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;

namespace ApiScanner.Core
{
    public sealed class ExchangeStoreScanner : IScanner
    {
        private TopologyScanner _TopologyScanner;
        private ExchangeStoreScannerTestConfigurationElement _Configuration;

        public void Initialize(TestConfigurationElement configuration)
        {
            if (configuration == null) throw new ArgumentNullException("configuration");
            _Configuration = (ExchangeStoreScannerTestConfigurationElement) configuration;
            _TopologyScanner = new TopologyScanner(_Configuration.Domain, _Configuration.Servers.Count == 0 ? _Configuration.Servers.Select(element => element.Name).ToArray() : null);
            _TopologyScanner.ScanTopology();
        }

        public IEnumerable<Artifact> EnumerateArtifacts(Action<string> progressAction)
        {
            if (progressAction == null) throw new ArgumentNullException("progressAction");
            
            if (_Configuration.ScanPrivateFolders)
            {
                progressAction("Scanning private mailboxes");
                foreach (var artifact in ScanMailboxes(progressAction))
                {
                    yield return artifact;
                }
            }
            if (_Configuration.ScanPublicFolders)
            {
                progressAction("Scanning public folders");
                foreach (var artifact in ScanPublicFolders(progressAction))
                {
                    yield return artifact;
                }
            }
        }

        IEnumerable<Artifact> ScanMailboxes(Action<string> progressAction)
        {
            foreach (var mdb in _TopologyScanner.Mdbs)
            {
                progressAction(string.Format("Scanning {0}", mdb.Name));
                if ((mdb.Type != MdbType.Private) || (mdb.OwningServerBL == null) ||(mdb.OwningServerBL.SerialNumber >= 14)) continue;

                var mailboxes = _TopologyScanner.EnumerateMailboxes(mdb);

                foreach (var mailbox in mailboxes.Where(mailbox => mailbox.MailboxType != MailboxType.Unknown))
                {
                    progressAction(string.Format("Scanning mailbox {0}", mailbox.SmtpAddress));

                    IEnumerable<StoreEvent> storeEvents = null;

                    Exception exception = null;
                    try
                    {
                        storeEvents = EventFinder.GetEvents(mdb.OwningServerBL.NetworkAddress,
                                                            mailbox.SmtpAddress, mailbox.MailboxType,
                                                            mdb.OwningServerBL.RequiresSsl);
                    }
                    catch (WebException e)
                    {
                        if (((HttpWebResponse) e.Response).StatusCode == HttpStatusCode.Forbidden
                            && !mdb.OwningServerBL.RequiresSsl) // We got a 403, and we weren't using SSL
                        {
                            // Let's try again with SSL
                            storeEvents = EventFinder.GetEvents(mdb.OwningServerBL.NetworkAddress,
                                                                mailbox.SmtpAddress, mailbox.MailboxType, true);
                            // if SSL worked, let's use it for other mailboxes on this server
                            if (storeEvents != null) mdb.OwningServerBL.RequiresSsl = true;
                        }
                        else
                        {
                            exception = e;
                        }
                    }
                    catch (Exception e)
                    {
                        exception = e;
                    }

                    if (exception != null)
                    {
                        yield return new ScannerError(string.Format("Mailbox of user {0}", mailbox.SmtpAddress), exception);
                    }
                    if (storeEvents == null) continue;

                    foreach (var storeEvent in storeEvents)
                    {
                        yield return new StoreEventBinding(storeEvent, mdb.Type, mdb.OwningServerBL.SerialNumberString);
                    }
                }
            }
        }

        IEnumerable<Artifact> ScanPublicFolders(Action<string> progressAction)
        {
            foreach (var mdb in _TopologyScanner.Mdbs)
            {
                progressAction(string.Format("Scanning {0}", mdb.Name));

                if ((mdb.Type != MdbType.Public) || (mdb.OwningServerBL.SerialNumber >= 14)) continue;

                var artifacts = _TopologyScanner.EnumerateAndScanPublicFolders(mdb.OwningServerBL.NetworkAddress,
                                                                               mdb.OwningServerBL.RequiresSsl, _Configuration.Domain,
                                                                               progressAction);
                var mdbSuccessfulCrawled = true;
                foreach (var artifact in artifacts)
                {
                    yield return artifact;
                    if (artifact is FatalScannerError)
                    {
                        mdbSuccessfulCrawled = false;
                    }
                }
                if (mdbSuccessfulCrawled)
                {
                    yield break;
                }
            }
        }
    }
}